Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 | export const dynamic = "force-dynamic"; import { NextRequest, NextResponse } from "next/server"; import { prisma } from "@/lib/prisma"; import { logger } from "@/lib/logging"; import { affiliateSystem } from "@/lib/affiliate-system"; import { nanoid } from "nanoid"; import { successResponse, ApiError, ApiSuccessResponse, ApiErrorResponse, handleApiError } from "@/lib/api"; // Cookie settings const COOKIE_NAME = "affiliate_ref"; const COOKIE_MAX_AGE = 30 * 24 * 60 * 60; // 30 days in seconds interface RouteParams { params: Promise<{ code: string }>; } /** * GET /api/affiliates/[code]/track * Track an affiliate click and redirect to destination */ export async function GET(request: NextRequest, { params }: RouteParams) { try { const { code } = await params; // Find the affiliate by code const affiliate = await prisma.affiliate.findUnique({ where: { code }}); if (!affiliate || affiliate.status !== "ACTIVE") { // Redirect to homepage if invalid affiliate return NextResponse.redirect( new URL("/", process.env.NEXT_PUBLIC_BASE_URL || request.url) ); } // Get tracking data const { searchParams } = new URL(request.url); const redirect = searchParams.get("redirect") || "/"; const ipAddress = request.headers.get("x-forwarded-for")?.split(",")[0]?.trim() || request.headers.get("x-real-ip") || "unknown"; const userAgent = request.headers.get("user-agent") || ""; const referrer = request.headers.get("referer") || ""; // Generate or get visitor ID let visitorId = request.cookies.get("visitor_id")?.value; if (!visitorId) { visitorId = nanoid(16); } // Track the click await affiliateSystem.trackClick({ affiliateCode: affiliate.code, visitorId, ipAddress, userAgent, referer: referrer, landingPage: redirect}); logger.info("Affiliate click tracked", { category: "API", affiliateCode: affiliate.code, visitorId}); // Create redirect response with cookies const response = NextResponse.redirect( new URL(redirect, process.env.NEXT_PUBLIC_BASE_URL || request.url) ); // Set affiliate cookie response.cookies.set(COOKIE_NAME, affiliate.code, { maxAge: COOKIE_MAX_AGE, path: "/", httpOnly: true, secure: process.env.NODE_ENV === "production", sameSite: "lax"}); // Set visitor ID cookie if new if (!request.cookies.get("visitor_id")) { response.cookies.set("visitor_id", visitorId, { maxAge: COOKIE_MAX_AGE, path: "/", httpOnly: true, secure: process.env.NODE_ENV === "production", sameSite: "lax"}); } return response; } catch (error) { logger.error("Error tracking affiliate click", error instanceof Error ? error : new Error(String(error)), { category: "API" }); // On error, still redirect to prevent bad user experience return NextResponse.redirect( new URL("/", process.env.NEXT_PUBLIC_BASE_URL || request.url) ); } } /** * POST /api/affiliates/[code]/track * Track a click via API (for programmatic tracking) */ export async function POST( request: NextRequest, { params }: RouteParams ): Promise<NextResponse<ApiSuccessResponse<unknown> | ApiErrorResponse>> { try { const { code } = await params; // Find the affiliate by code const affiliate = await prisma.affiliate.findUnique({ where: { code }}); if (!affiliate || affiliate.status !== "ACTIVE") { throw ApiError.notFound("Affiliate"); } const body = await request.json(); const ipAddress = request.headers.get("x-forwarded-for")?.split(",")[0]?.trim() || request.headers.get("x-real-ip") || "unknown"; const userAgent = request.headers.get("user-agent") || ""; // Generate visitor ID if not provided const visitorId = body.visitorId || nanoid(16); // Track the click const result = await affiliateSystem.trackClick({ affiliateCode: affiliate.code, visitorId, ipAddress, userAgent, referer: body.referrer || "", landingPage: body.landingPage || "/"}); if (!result.success) { throw ApiError.internal("Failed to track click"); } logger.info("Affiliate click tracked via API", { category: "API", affiliateCode: affiliate.code, visitorId, clickId: result.clickId}); return successResponse({ clickId: result.clickId, visitorId, affiliateCode: affiliate.code}); } catch (error) { return handleApiError(error, `POST /api/affiliates/[code]/track`); } } |